<!DOCTYPE html>
<html lang="zh-CN">

        <div class="blog-content-box">
  <div class="article-header-box">
    <div class="article-header">
      <div class="article-title-box">
              <h1 class="title-article">创建对象与使用对象——谈谈工厂的作用</h1>
      </div>

    </div>
  </div>
  <article class="baidu_pl">
    <div id="article_content" class="article_content clearfix csdn-tracking-statistics" data-pid="blog" data-mod=popu_307 data-dsm="post">
             
             
            <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-f57960eb32.css" />
                              <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-f57960eb32.css" />
          <div class="htmledit_views" id="content_views">
            <p align="left"><span style="font-size:16px;"> <span style="font-size:18px;">     在设计模式的教学和推广过程中，很多企业学员和在校学生经常问我，工厂模式（包括简单工厂模式、工厂方法模式和抽象工厂模式）到底有什么用，很多时候通过反射机制就可以很灵活地创建对象，为毛还要工厂？<img alt="微笑" src="http://static.blog.csdn.net/xheditor/xheditor_emot/default/smile.gif" />，在本文中我将围绕<span style="font-size:16px;">创建</span>对象和<span style="font-size:16px;">使用</span>对象来简单谈谈工厂的作用。</span></span></p><p><span style="font-size:16px;"><span style="font-size:18px;">      与一个对象相关的职责通常有三类：<strong><span style="color:#ff0000;">对象本身所具有的职责、创建对象的职责和使用对象的职责</span></strong>。对象本身的职责比较容易理解，就是对象自身所具有的一些数据和行为，可通过一些公开的方法来实现它的职责。在本文中，我们将简单讨论一下对象的创建职责和使用职责。</span></span></p><p><span style="font-size:16px;"><span style="font-size:18px;">      在<span style="font-family:'Times New Roman';">Java</span>语言中，我们通常有以下几种创建对象的方式：</span></span></p><p><span style="font-size:16px;"><span style="font-size:18px;"><span style="font-family:'Times New Roman';">       (1) </span>使用<span style="font-family:'Times New Roman';">new</span>关键字直接创建对象；</span></span></p><p><span style="font-size:16px;"><span style="font-size:18px;"><span style="font-family:'Times New Roman';">       (2) </span>通过反射机制创建对象；</span></span></p><p><span style="font-size:16px;"><span style="font-size:18px;"><span style="font-family:'Times New Roman';">       (3) </span>通过<span style="font-family:'Times New Roman';">clone()</span>方法创建对象；</span></span></p><p><span style="font-size:16px;"><span style="font-size:18px;"><span style="font-family:'Times New Roman';">       (4) </span>通过工厂类创建对象。</span></span></p><p><span style="font-size:18px;"><span style="font-size:16px;">     <span style="font-size:18px;"> 毫无疑问，在客户端代码中直接使用<span style="font-family:'Times New Roman';">new</span>关键字是最简单的一种创建对象的方式，但是它的灵活性较差，下面通过一个简单的示例来加以说明：</span></span> </span></p><pre><code class="language-java"></code></pre><p><span style="font-size:16px;">      <span style="font-size:18px;">在<span style="font-family:'Times New Roman';">LoginAction</span>类中定义了一个<span style="font-family:'Times New Roman';">UserDAO</span>类型的对象<span style="font-family:'Times New Roman';">udao</span>，在<span style="font-family:'Times New Roman';">LoginAction</span>的构造函数中创建了<span style="font-family:'Times New Roman';">JDBCUserDAO</span>类型的<span style="font-family:'Times New Roman';">udao</span>对象，并在<span style="font-family:'Times New Roman';">execute()</span>方法中调用了<span style="font-family:'Times New Roman';">udao</span>对象的<span style="font-family:'Times New Roman';">findUserById()</span>方法，这段代码看上去并没有什么问题。下面我们来分析一下<span style="font-family:'Times New Roman';">LoginAction</span>和<span style="font-family:'Times New Roman';">UserDAO</span>之间的关系，<span style="font-family:'Times New Roman';">LoginAction</span>类负责创建了一个<span style="font-family:'Times New Roman';">UserDAO</span>子类的对象并使用<span style="font-family:'Times New Roman';">UserDAO</span>的方法来完成相应的业务处理，也就是说<span style="font-family:'Times New Roman';">LoginAction</span>即负责<span style="font-family:'Times New Roman';">udao</span>的创建又负责<span style="font-family:'Times New Roman';">udao</span>的使用，创建对象和使用对象的职责耦合在一起，这样的设计会导致一个很严重的问题：如果在<span style="font-family:'Times New Roman';">LoginAction</span>中希望能够使用<span style="font-family:'Times New Roman';">UserDAO</span>的另一个子类如<span style="font-family:'Times New Roman';">HibernateUserDAO</span>类型的对象，必须修改<span style="font-family:'Times New Roman';">LoginAction</span>类的源代码，违反了“开闭原则”。如何解决该问题？</span></span></p><p><span style="font-size:16px;"><span style="font-family:'Times New Roman';font-size:18px;">      最常用的一种解决方法是将udao对象的创建职责从LoginAction类中移除，在LoginAction类之外创建对象，那么谁来负责创建UserDAO对象呢？答案是：工厂类。通过引入工厂类，客户类（如LoginAction）不涉及对象的创建，对象的创建者也不会涉及对象的使用。引入工厂类UserDAOFactory之后的结构如图1所示：</span></span></p><p align="center"><img style="width:637px;" alt="" src="https://img-my.csdn.net/uploads/201205/16/1337135120_7984.jpg" width="534" height="470" /></p><p align="center"><strong>图<span style="font-family:'Times New Roman';">1 </span>引入工厂类之后的结构图</strong></p><p align="left"> <span style="font-size:16px;">     <span style="font-size:18px;"> 工厂类的引入将降低因为产品或工厂类改变所造成的维护工作量。如果<span style="font-family:'Times New Roman';">UserDAO</span>的某个子类的构造函数发生改变或者要需要添加或移除不同的子类，只要维护<span style="font-family:'Times New Roman';">UserDAOFactory</span>的代码，而不会影响到<span style="font-family:'Times New Roman';">LoginAction</span>；如果<span style="font-family:'Times New Roman';">UserDAO</span>的接口发生改变，例如添加、移除方法或改变方法名，只需要修改<span style="font-family:'Times New Roman';">LoginAction</span>，不会给<span style="font-family:'Times New Roman';">UserDAOFactory</span>带来任何影响。</span></span></p><p><span style="font-size:16px;"><span style="font-size:18px;"><span style="font-family:'Times New Roman';">       </span>在所有的工厂模式中，我们都强调一点：<strong>两个类<span style="font-family:'Times New Roman';">A</span>和<span style="font-family:'Times New Roman';">B</span>之间的关系应该仅仅是<span style="font-family:'Times New Roman';">A</span>创建<span style="font-family:'Times New Roman';">B</span>或是<span style="font-family:'Times New Roman';">A</span>使用<span style="font-family:'Times New Roman';">B</span>，而不能两种关系都有</strong>。将对象的创建和使用分离，也使得系统更加符合“单一职责原则”，有利于对功能的复用和系统的维护。</span></span></p><p><span style="font-size:16px;"><span style="font-size:18px;"><span style="font-family:'Times New Roman';">       </span>此外，将对象的创建和使用分离还有一个好处：<strong><span style="color:#ff0000;">防止用来实例化一个类的数据和代码在多个类中到处都是，可以将有关创建的知识搬移到一个工厂类中</span></strong>，这在<span style="font-family:'Times New Roman';">Joshua Kerievsky</span>的《重构与模式》一书中有专门的一节来进行介绍。因为有时候我们创建一个对象不只是简单调用其构造函数，还需要设置一些参数，可能还需要配置环境，如果将这些代码散落在每一个创建对象的客户类中，势必会出现<strong>代码重复、创建蔓延</strong>的问题，而这些客户类其实无须承担对象的创建工作，它们只需使用已创建好的对象就可以了。此时，可以引入工厂类来封装对象的创建逻辑和客户代码的实例化<span style="font-family:'Times New Roman';">/</span>配置选项。</span></span></p><p><span style="font-size:16px;"><span style="font-size:18px;">      使用工厂类还有一个“不是特别明显的”优点，一个类可能拥有多个构造函数，而在<span style="font-family:'Times New Roman';">Java</span>、<span style="font-family:'Times New Roman';">C#</span>等语言中构造函数名字都与类名相同，客户端只能通过传入不同的参数来调用不同的构造函数创建对象，从构造函数和参数列表中也许大家根本不了解不同构造函数所构造的产品的差异。但如果将对象的创建过程封装在工厂类中，我们<strong><span style="color:#ff0000;">可以提供一系列名字完全不同的工厂方法，每一个工厂方法对应一个构造函数</span></strong>，客户端可以以一种更加可读、易懂的方式来创建对象，而且，从一组工厂方法中选择一个意义明确的工厂方法，比从一组名称相同参数不同的构造函数中选择一个构造函数要方便很多。如图<span style="font-family:'Times New Roman';">2</span>所示：</span></span></p><p align="center"><img style="width:696px;" alt="" src="https://img-my.csdn.net/uploads/201204/30/1335722150_7873.jpg" width="699" height="133" /></p><div align="left"> <span style="font-size:16px;">      <span style="font-size:18px;">在图<span style="font-family:'Times New Roman';">2</span>中，矩形工厂类<span style="font-family:'Times New Roman';">RectangleFactory</span>提供了两个工厂方法<span style="font-family:'Times New Roman';">createRectangle()</span>和<span style="font-family:'Times New Roman';">createSquare()</span>，一个用于创建长方形，一个用于创建正方形，这两个方法比直接通过构造函数来创建长方形或正方形对象意义更加明确，也在一定程度上降低了客户端调用时出错的概率。</span></span></div><p><span style="font-size:16px;"><span style="font-size:18px;">      那么，有人可能会问，是否需要为设计中的每一个类都配备一个工厂类？答案是：具体情况具体分析。如果产品类很简单，而且不存在太多变数，其构造过程也很简单，此时无须为其提供工厂类，直接在使用之前实例化即可，例如<span style="font-family:'Times New Roman';">Java</span>语言中的<span style="font-family:'Times New Roman';">String</span>类，我们就无须为它专门提供一个<span style="font-family:'Times New Roman';">StringFactory</span>，这样做反而有点像杀鸡用牛刀，大材小用，而且会导致工厂泛滥，增加系统的复杂度。</span></span></p><p align="center"><span style="font-size:14px;">【作者：刘伟  </span><a href="http://blog.csdn.net/lovelion" rel="nofollow"><span style="font-size:14px;">http://blog.csdn.net/lovelion</span></a><span style="font-size:14px;">】</span></p>          </div>
                  </div>
  </article>
</div>


</html>
